package net.qiujuer.genius.graphics;

import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;

/**
 * In this can blurring your image by
 * The Blurring support:
 */
@SuppressWarnings("JavaDoc")
public final class Blur {
    /**
     * Building the pixelMap
     *
     * @param original pixelMap
     * @param radius radius
     * @return PixelMap
     */
    private static PixelMap checkSource(PixelMap original, int radius) {
        if (radius < 0 || radius > 256) {
            throw new RuntimeException("Blur pixelMap radius must >= 1 and <=256.");
        }

        // First we should check the original
        if (original == null) {
            throw new NullPointerException("Blur pixelMap original isn't null.");
        }
        if (original.isReleased()) {
            throw new RuntimeException("Blur pixelMap can't blur a recycled pixelMap.");
        }
        PixelFormat config = original.getImageInfo().pixelFormat;
        if (config != PixelFormat.ARGB_8888 && config != PixelFormat.RGB_565) {
            throw new RuntimeException("Blur pixelMap only supported PixelFormat.ARGB_8888 and PixelFormat.RGB_565.");
        }

        return (original);
    }

    /**
     * StackBlur By Jni pixelMap
     *
     * @param original Original Image
     * @param radius Blur radius
     * @return Image PixelMap
     */
    public static PixelMap onStackBlur(PixelMap original, int radius) {
        PixelMap pixelMap = checkSource(original, radius);

        // Return this none blur
        if (radius == 1) {
            return pixelMap;
        }

        //Jni BitMap Blur
        nativeStackBlurBitmap(pixelMap, radius);

        return (pixelMap);
    }

    /**
     * StackBlur By Jni PixelMap
     * in this we will cut the source PixelMap to some parts.
     * We'll deal(blur) with it one by one. This will reduce the memory consumption.
     *
     * @param original Original Image
     * @param radius Blur radius
     * @return Image PixelMap
     */
    public static PixelMap onStackBlurClip(PixelMap original, int radius) {
        PixelMap pixelMap = checkSource(original, radius);

        // Return this none blur
        if (radius == 1) {
            return pixelMap;
        }

        int h = pixelMap.getImageInfo().size.height;
        int w = pixelMap.getImageInfo().size.width;

        final float clipPixels = 1024 * 256;
        float clipScale = (h * w) / clipPixels;
        int minLen = radius + radius + 50;

        if (clipScale >= 2) {
            float itemLen = h / clipScale;
            itemLen = itemLen < minLen ? minLen : itemLen;
            clipScale = h / itemLen;
        }

        if (clipScale < 2) {
            // Jni pixelMap Blur
            nativeStackBlurBitmap(pixelMap, radius);
        } else {
            if (clipScale > 12) {
                clipScale = 12;
            }

            // Jni pixelMap Blur
            onStackBlurClip(pixelMap, radius, (int) clipScale);
        }

        return (pixelMap);
    }

    /**
     * StackBlur By Jni PixelMap
     * in this we will cut the source PixelMap to some parts.
     * We'll deal(blur) with it one by one. This will reduce the memory consumption.
     *
     * @param original Original Image
     * @param radius Blur radius
     * @param parts Blur cut parts count
     * @return Image PixelMap
     */
    public static PixelMap onStackBlurClip(PixelMap original, int radius, int parts) {
        PixelMap pixelMap = checkSource(original, radius);
        if (parts < 2 || parts > 12) {
            throw new RuntimeException("Blur pixelMap parts must >= 2 and <=12.");
        }

        if (original.getImageInfo().size.height / parts < radius + radius) {
            throw new RuntimeException("Blur PixelMap height/partsCount must > radius+radius values.");
        }

        //Jni PixelMap Blur
        nativeStackBlurBitmapClip(pixelMap, radius, parts);

        return (pixelMap);
    }

    /**
     * StackBlur By Jni Pixels
     *
     * @param pix `Original` Image, you can call:
     * <p>
     * int w = PixelMap.getWidth();
     * int h = PixelMap.getHeight();
     * int[] pix = new int[w * h];
     * PixelMap.getPixels(pix, 0, w, 0, 0, w, h);
     * <p>
     * // Jni Pixels Blur
     * onStackBlurPixels(pix, w, h, radius);
     * <p>
     * PixelMap.setPixels(pix, 0, w, 0, 0, w, h);
     * <p>
     * @param w
     * @param h
     * @param radius
     * @return int[]
     */
    public static int[] onStackBlurPixels(int[] pix, int w, int h, int radius) {
        if (radius < 0 || radius > 256) {
            throw new RuntimeException("Blur PixelMap radius must >= 1 and <=256.");
        }

        if (pix == null) {
            throw new RuntimeException("Blur PixelMap pix isn't null.");
        }

        if (pix.length < w * h) {
            throw new RuntimeException("Blur PixelMap pix length must >= w * h.");
        }

        // Jni Pixels Blur
        nativeStackBlurPixels(pix, w, h, radius);

        return (pix);
    }

    /**
     * StackBlur By Java PixelMap
     *
     * @param original Original Image
     * @param radius Blur radius
     * @return Image PixelMap
     */
    public static PixelMap onStackBlurJava(PixelMap original, int radius) {
        // Stack Blur v1.0 from
        // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
        //
        // Java Author: Mario Klingemann <mario at quasimondo.com>
        // http://incubator.quasimondo.com
        // created Feburary 29, 2004
        // http://www.kayenko.com
        // ported april 5th, 2012

        // This is a compromise between Gaussian Blur and Box blur
        // It creates much better looking blurs than Box Blur, but is
        // 7x faster than my Gaussian Blur implementation.
        //
        // I called it Stack Blur because this describes best how this
        // filter works internally: it creates a kind of moving stack
        // of colors whilst scanning through the image. Thereby it
        // just has to add one new block of color to the right side
        // of the stack and remove the leftmost color. The remaining
        // colors on the topmost layer of the stack are either added on
        // or reduced by one, depending on if they are on the right or
        // on the left side of the stack.
        //
        // If you are using this algorithm in your code please add
        // the following line:
        //
        // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>

        PixelMap pixelMap = checkSource(original, radius);

        // Return this none blur
        if (radius == 1) {
            return pixelMap;
        }

        int w = pixelMap.getImageInfo().size.width;
        int h = pixelMap.getImageInfo().size.height;

        int[] pix = new int[w * h];
        // get array
        pixelMap.readPixels(pix, 0, w, new Rect(0, 0, w, h));

        // run Blur
        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        short r[] = new short[wh];
        short g[] = new short[wh];
        short b[] = new short[wh];
        int rSum, gSum, bSum, x, y, i, p, yp, yi, yw;
        int vMin[] = new int[Math.max(w, h)];

        int divSum = (div + 1) >> 1;
        divSum *= divSum;

        short dv[] = new short[256 * divSum];
        for (i = 0; i < 256 * divSum; i++) {
            dv[i] = (short) (i / divSum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackPointer;
        int stackStart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routSum, goutSum, boutSum;
        int rinSum, ginSum, binSum;

        for (y = 0; y < h; y++) {
            rinSum = ginSum = binSum = routSum = goutSum = boutSum = rSum = gSum = bSum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rbs = r1 - Math.abs(i);
                rSum += sir[0] * rbs;
                gSum += sir[1] * rbs;
                bSum += sir[2] * rbs;
                if (i > 0) {
                    rinSum += sir[0];
                    ginSum += sir[1];
                    binSum += sir[2];
                } else {
                    routSum += sir[0];
                    goutSum += sir[1];
                    boutSum += sir[2];
                }
            }
            stackPointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rSum];
                g[yi] = dv[gSum];
                b[yi] = dv[bSum];

                rSum -= routSum;
                gSum -= goutSum;
                bSum -= boutSum;

                stackStart = stackPointer - radius + div;
                sir = stack[stackStart % div];

                routSum -= sir[0];
                goutSum -= sir[1];
                boutSum -= sir[2];

                if (y == 0) {
                    vMin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vMin[x]];

                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rinSum += sir[0];
                ginSum += sir[1];
                binSum += sir[2];

                rSum += rinSum;
                gSum += ginSum;
                bSum += binSum;

                stackPointer = (stackPointer + 1) % div;
                sir = stack[(stackPointer) % div];

                routSum += sir[0];
                goutSum += sir[1];
                boutSum += sir[2];

                rinSum -= sir[0];
                ginSum -= sir[1];
                binSum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinSum = ginSum = binSum = routSum = goutSum = boutSum = rSum = gSum = bSum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rSum += r[yi] * rbs;
                gSum += g[yi] * rbs;
                bSum += b[yi] * rbs;

                if (i > 0) {
                    rinSum += sir[0];
                    ginSum += sir[1];
                    binSum += sir[2];
                } else {
                    routSum += sir[0];
                    goutSum += sir[1];
                    boutSum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackPointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = (0xff000000 & pix[yi]) | (dv[rSum] << 16) | (dv[gSum] << 8) | dv[bSum];

                rSum -= routSum;
                gSum -= goutSum;
                bSum -= boutSum;

                stackStart = stackPointer - radius + div;
                sir = stack[stackStart % div];

                routSum -= sir[0];
                goutSum -= sir[1];
                boutSum -= sir[2];

                if (x == 0) {
                    vMin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vMin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinSum += sir[0];
                ginSum += sir[1];
                binSum += sir[2];

                rSum += rinSum;
                gSum += ginSum;
                bSum += binSum;

                stackPointer = (stackPointer + 1) % div;
                sir = stack[stackPointer];

                routSum += sir[0];
                goutSum += sir[1];
                boutSum += sir[2];

                rinSum -= sir[0];
                ginSum -= sir[1];
                binSum -= sir[2];

                yi += w;
            }
        }

        // set PixelMap
        PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
        initializationOptions.size = new Size(w, h);
//        pixelMap.writePixels(pix,0, w,new Rect(0, 0, w, h));
        pixelMap = PixelMap.create(pix, initializationOptions);
        return (pixelMap);
    }

    static {
        System.loadLibrary("genius_graphics");
    }

    /**
     * Blur Image By Pixels
     *
     * @param pixels Img pixel array
     * @param w Img width
     * @param h Img height
     * @param r Blur radius
     * @hide
     */
    private static native void nativeStackBlurPixels(int[] pixels, int w, int h, int r);

    /**
     * Blur Image By pixelMap
     *
     * @param pixelMap Img pixelMap
     * @param r Blur radius
     * @hide
     */
    private static native void nativeStackBlurBitmap(PixelMap pixelMap, int r);

    /**
     * Blur image by pixelMap,
     * in this we will cut the source pixelMap to some parts.
     * We'll deal(blur) with it one by one. This will reduce the memory consumption.
     *
     * @param pixelMap Img pixelMap
     * @param r Blur radius
     * @param parts Cut the image to parts by the vertical orientation
     * @hide
     */
    private static native void nativeStackBlurBitmapClip(PixelMap pixelMap, int r, int parts);
}
